# -*- coding: utf-8 -*-
from hashlib import md5
from datetime import datetime
import time

from future.utils import raise_with_traceback
from sqlalchemy.exc import SQLAlchemyError

from common.console.model import *
from common.utils import exceptions as err
from common.utils.respcode import StatusCode
from common.utils import id_generator
from common.utils.tz import to_ts
from common.utils.db import (get_orderby, parse_query_dct, list_object,
                             paginate, generate_filter, upsert, get_count)

from common.utils.db import list_object, get, upsert, delete

_SALT = u"%#fEg*PY"


def encode_password(passwd):
    return md5(passwd.encode('utf-8') + _SALT).hexdigest()


def sql_wrapper(func):
    def _wrapper(*args, **kwargs):
        try:
            return func(*args, **kwargs)
        except SQLAlchemyError as e:
            orm.session.rollback()
            raise_with_traceback(err.DbError(e))
        except err.Error:
            orm.session.rollback()
            raise
        except Exception as e:
            orm.session.rollback()
            raise_with_traceback(err.Error(e))
        finally:
            orm.session.close()

    return _wrapper


@sql_wrapper
def create_user(username, password, nickname='',
                role=FORBIDDEN_ROLE):
    user = User()
    user.username = username
    user.password = encode_password(password)
    user.nickname = nickname
    user.role = role
    user.enabled = USER_ENABLE
    user.deleted = USER_NOT_DELETED
    user.created_at = user.updated_at = datetime.utcnow()

    user.save()


@sql_wrapper
def update_user(user_id, user_info):
    user = User.query.with_for_update().filter(
        User.id == user_id).first()
    if not user:
        raise_with_traceback(err.AuthenticateError(
            status=StatusCode.INVALID_USER))

    if 'password' in user_info:
        user_info['password'] = encode_password(user_info['password'])

    for k, v in user_info.iteritems():
        setattr(user, k, v)

    user.save()
    return user


@sql_wrapper
def list_users(query_dct):
    query_dct['deleted'] = str(USER_NOT_DELETED)

    # user can only see lower level users
    query_dct = parse_query_dct(query_dct, User)
    query = User.query.filter(generate_filter(query_dct, User))
    total_count = get_count(query)
    orderby = get_orderby(query_dct.get('$orderby'), User)
    if orderby is not None:
        query = query.order_by(orderby)
    query = paginate(query, query_dct)
    return query.all(), total_count


@sql_wrapper
def create_role(rolename, permissions, mch_ids):
    role = Role()
    role.rolename = rolename
    role.permissions = ','.join([str(p) for p in permissions])
    role.mch_ids = mch_ids
    role.deleted = USER_NOT_DELETED
    role.enabled = USER_ENABLE
    role.save()
    return role


@sql_wrapper
def get_mchids_filter_by_user(user_id):
    u = User.query.filter(User.id == user_id).first()
    if u is None:
        return None
    role = Role.query.filter(Role.id == u.role).first()
    if role is None:
        return None
    if role.mch_ids is None or role.mch_ids == '':
        return None
    return {"$in": [int(m) for m in role.mch_ids.split(',')]}


@sql_wrapper
def get_role(role_id):
    role = Role.query.filter(Role.id == role_id).first()
    if not role:
        raise err.DataError('role id %s not exist' % role_id)
    return role


@sql_wrapper
def update_role(role_id, role_info):
    role = Role.query.with_for_update().filter(
        Role.id == role_id).first()
    if not role:
        raise err.ParamError('role not exist')

    for k, v in role_info.iteritems():
        if k in ['permissions', ]:
            v = ','.join([str(i) for i in v])
        setattr(role, k, v)

    role.save()
    return role


@sql_wrapper
def list_roles(query_dct):
    query_dct['deleted'] = str(USER_NOT_DELETED)

    # user can only see lower level users
    query_dct = parse_query_dct(query_dct, Role)
    query = Role.query.filter(generate_filter(query_dct, Role))
    total_count = get_count(query)
    orderby = get_orderby(query_dct.get('$orderby'), Role)
    if orderby is not None:
        query = query.order_by(orderby)
    query = paginate(query, query_dct)
    return query.all(), total_count


@sql_wrapper
def delete_role(role_id):
    user = User.query.filter(User.role == role_id).all()
    if user:
        raise err.ParamError('User in this role')
    Role.query.filter(Role.id == role_id).update({
        'deleted': 1,
        'mch_ids': '',
        'permissions': ''
    })
    orm.session.commit()
    return {}


@sql_wrapper
def get_user(user_id):
    return User.query.filter(User.id == user_id).first()


@sql_wrapper
def delete_user(user_id):
    UserToken.query.filter(UserToken.user_id == user_id).delete()
    User.query.filter(User.id == user_id).update({'deleted': 1, 'role': 0})
    orm.session.commit()


@sql_wrapper
def login_user(username, password):
    user = User.query.filter(
        User.username == username).filter(
        User.deleted == USER_NOT_DELETED).filter(
        User.enabled == USER_ENABLE).first()
    if not user:
        raise err.AuthenticateError(status=StatusCode.INVALID_USER)

    if user.password != encode_password(password):
        raise err.AuthenticateError(status=StatusCode.WRONG_PASSWORD)

    UserToken.query.filter(UserToken.user_id == user.id).delete()

    user_token = UserToken()
    user_token.token = id_generator.generate_uuid()
    user_token.user_id = user.id
    user_token.save()
    orm.session.commit()

    user_info = user.as_dict()
    user_info['token'] = user_token.token
    user_info.pop('password', '')
    return user_info


@sql_wrapper
def logout_user(user_id):
    UserToken.query.filter(
        UserToken.user_id == user_id).delete()
    orm.session.commit()


@sql_wrapper
def get_online_info(user_id, token):
    usertoken = UserToken.query.filter(
        UserToken.user_id == user_id).filter(
        UserToken.token == token).first()
    if usertoken:
        if int(time.time()) - to_ts(usertoken.updated_at) >= 1200:
            usertoken.delete()
            return None
    else:
        return None
    usertoken.updated_at = datetime.utcnow()
    usertoken.save()
    return usertoken


@sql_wrapper
def insert_record(info):
    return upsert(Record, info)


@sql_wrapper
def list_record(query_dct):
    username = query_dct.pop('username', None)
    query = orm.session.query(Record, User.username).join(
        User, Record.operator == User.id)
    if username is not None:
        query = query.filter(User.username.like('%%%s%%' % username))
    query_dct = parse_query_dct(query_dct, Record)
    query = query.filter(generate_filter(query_dct, Record))
    total_count = get_count(query)
    orderby = get_orderby(query_dct.get('$orderby'), Record)
    if orderby is not None:
        query = query.order_by(orderby)
    query = paginate(query, query_dct)
    return query.all(), total_count


@sql_wrapper
def list_perm(query_dct):
    return list_object(query_dct, Permission)


@sql_wrapper
def get_perm_id(url, action):
    return Permission.query.filter(
        Permission.url == url).filter(
        Permission.action == action).first()


@sql_wrapper
def list_perm_mod():
    return PERMISSION_MOD.to_dict()
